#include "rabbitvcs_plugin.h"
#include "rabbitvcs_action.h"
#include <KFileItem>
#include <QProcess>
#include <QDir>
#include <QDBusMessage>
#include <QDBusConnection>
#include <QJsonDocument>
#include <QJsonObject>

RabbitVcsPlugin::RabbitVcsPlugin(QObject *parent)
    : KVersionControlPlugin(parent),
      currentVcs(RabbitVcsType::none),
      cmdProcess(new QProcess(this))
{
    init();
}

RabbitVcsPlugin::~RabbitVcsPlugin()
{}

QString RabbitVcsPlugin::fileName() const
{
    // 仅为禁止通过vcs数据目录判断仓库
    return QString::fromUtf8(".xxxxxxxxxxx");
}

QString RabbitVcsPlugin::localRepositoryRoot(const QString &directory) const
{
    QDir dir(directory);
    while (!dir.isRoot())
    {
        if (dir.exists(QString::fromUtf8(".svn")))
        {
            currentVcs = RabbitVcsType::svn;
            return dir.absolutePath();
        }
        if (dir.exists(QString::fromUtf8(".git")))
        {
            currentVcs = RabbitVcsType::git;
            return dir.absolutePath();
        }
        dir.cdUp();
    }
    currentVcs = RabbitVcsType::none;
    QDir dir2(directory);
    auto entries = dir2.entryList(QDir::Dirs, QDir::NoSort);
    for (auto &entry : entries)
    {
        if (QFile::exists(dir2.absoluteFilePath(entry) + QDir::separator() + QString::fromUtf8(".svn")))
        {
            return dir2.absolutePath();
        }
        if (QFile::exists(dir2.absoluteFilePath(entry) + QDir::separator() + QString::fromUtf8(".git")))
        {
            return dir2.absolutePath();
        }
    }
    return {};
}

bool RabbitVcsPlugin::beginRetrieval(const QString &directory)
{
    currentDir = QDir(directory).absolutePath();
    return true;
}

void RabbitVcsPlugin::endRetrieval()
{
}

KVersionControlPlugin::ItemVersion RabbitVcsPlugin::itemVersion(const KFileItem &item) const
{
    return checkStatus(item.localPath());
}

QList<QAction *> RabbitVcsPlugin::versionControlActions(const KFileItemList &items) const
{
    if (items.size() == 1 && items[0].localPath() == currentDirectory())
    {
        // 在当前目录空白处点击
        if (currentVcs == RabbitVcsType::none)
        {
            QList<QAction *> ret;
            ret.append(convertActions({"checkout"}, RabbitVcsType::svn));
            ret.append(convertActions({"clone"}, RabbitVcsType::git));
            return ret;
        }
        auto curDirVer = checkStatus(items[0].localPath());
        if (curDirVer == UnversionedVersion || curDirVer == IgnoredVersion)
        {
            return {};
        }
        setTargetItems(items);
        return convertActions({"update", "commit", "log", "cleanup"});
    }
    return {};
}

QList<QAction *> RabbitVcsPlugin::outOfVersionControlActions(const KFileItemList &items) const
{
    Q_UNUSED(items)
    setTargetItems(KFileItemList{});
    QList<QAction *> ret;
    ret.append(convertActions({"checkout"}, RabbitVcsType::svn));
    ret.append(convertActions({"clone"}, RabbitVcsType::git));
    return ret;
}

void RabbitVcsPlugin::init()
{
    connect(cmdProcess, &QProcess::started, [this]() {
        Q_EMIT processStarted();
    });
    connect(cmdProcess, &QProcess::finished, [this](int exitCode, QProcess::ExitStatus exitStatus) {
        Q_UNUSED(exitStatus);
        Q_EMIT processFinished(exitCode);
    });
    actions[RabbitVcsType::svn] = {};
    actions[RabbitVcsType::git] = {};
    addAction(RabbitVcsType::svn, "add", "SVN Add");
    addAction(RabbitVcsType::svn, "branch", "SVN Branch");
    addAction(RabbitVcsType::svn, "browser", "SVN Browser");
    addAction(RabbitVcsType::svn, "changes", "SVN Changes");
    addAction(RabbitVcsType::svn, "checkout", "SVN Checkout...");
    addAction(RabbitVcsType::svn, "cleanup", "SVN Cleanup");
    addAction(RabbitVcsType::svn, "commit", "SVN Commit");
    addAction(RabbitVcsType::svn, "delete", "SVN Delete");
    addAction(RabbitVcsType::svn, "log", "SVN Log");
    addAction(RabbitVcsType::svn, "revert", "SVN Revert");
    addAction(RabbitVcsType::svn, "update", "SVN Update");

    addAction(RabbitVcsType::git, "add", "GIT Add");
    addAction(RabbitVcsType::git, "branches", "GIT Branches");
    addAction(RabbitVcsType::git, "browser", "GIT Browser");
    addAction(RabbitVcsType::git, "changes", "GIT Changes");
    addAction(RabbitVcsType::git, "checkout", "GIT Checkout...");
    addAction(RabbitVcsType::git, "clean", "GIT Clean");
    addAction(RabbitVcsType::git, "clone", "GIT Clone");
    addAction(RabbitVcsType::git, "commit", "GIT Commit");
    addAction(RabbitVcsType::git, "delete", "GIT Delete");
    addAction(RabbitVcsType::git, "log", "GIT Log");
    addAction(RabbitVcsType::git, "merge", "GIT Merge");
    addAction(RabbitVcsType::git, "push", "GIT Push");
    addAction(RabbitVcsType::git, "revert", "GIT Revert");
    addAction(RabbitVcsType::git, "update", "GIT Update");
}

bool RabbitVcsPlugin::addAction(RabbitVcsType vcs, const char *name, const char *title, const char *icon)
{
    if (actions.contains(vcs))
    {
        auto &actionMap = actions[vcs];
        auto nameStr = QString::fromUtf8(name);
        if (actionMap.contains(nameStr))
        {
            delete actionMap[nameStr];
            actionMap.remove(nameStr);
        }
        auto newAction = new RabbitVcsAction(vcs, name, title, icon, this);
        connect(newAction, &RabbitVcsAction::actionTriggered, this, &RabbitVcsPlugin::actionTriggered);
        actionMap[nameStr] = newAction;
        return true;
    }
    return false;
}

KVersionControlPlugin::ItemVersion RabbitVcsPlugin::checkStatus(const QString &filePath) const
{
    auto message = QDBusMessage::createMethodCall(
        QString::fromUtf8("org.google.code.rabbitvcs.RabbitVCS.Checker"),
        QString::fromUtf8("/org/google/code/rabbitvcs/StatusChecker"),
        QString::fromUtf8("org.google.code.rabbitvcs.StatusChecker"),
        QString::fromUtf8("CheckStatus")
    );
    message.setArguments({filePath.toUtf8(), true, false, true});
    auto info = QDBusConnection::sessionBus().call(message);
    if (info.type() == QDBusMessage::ReplyMessage)
    {
        auto ver = info.arguments().takeFirst().toString();
        auto doc = QJsonDocument::fromJson(ver.toUtf8());
        if (doc.isObject())
        {
            auto obj = doc.object();
            QString key = QString::fromUtf8("summary");
            if (obj.contains(key))
            {
                return convertItemVersion(obj[key].toString());
            }
        }
    }
    else
    {
        qWarning() << "request rabbitvcs StatusChecker failed.";
    }
    return NormalVersion;
}

void RabbitVcsPlugin::setTargetItems(const KFileItemList &items) const
{
    targetItems.clear();
    for (auto &item : items)
        targetItems.append(item.localPath());
}

void RabbitVcsPlugin::runRabbitVcsCmd(const QString &module)
{
    if (cmdProcess->state() == QProcess::NotRunning)
    {
        QStringList args;
        args.append(module);
        if (!targetItems.isEmpty())
            args.append(targetItems);
        cmdProcess->start(QString::fromUtf8("rabbitvcs"), args);
    }
}

void RabbitVcsPlugin::runRabbitVcsCmd(const QString &module, const QStringList &args)
{
    if (cmdProcess->state() == QProcess::NotRunning)
    {
        QStringList args2;
        args2.append(module);
        args2.append(args);
        cmdProcess->start(QString::fromUtf8("rabbitvcs"), args2);
    }
}

KVersionControlPlugin::ItemVersion RabbitVcsPlugin::convertItemVersion(const QString &ver) const
{
    static QMap<QString, ItemVersion> versions = {
        {QString::fromUtf8("unknown"), UnversionedVersion},
        {QString::fromUtf8("unversioned"), UnversionedVersion},
        {QString::fromUtf8("normal"), NormalVersion},
        {QString::fromUtf8("updateRequired"), UpdateRequiredVersion},
        // {QString::fromUtf8("modified"), LocallyModifiedVersion},
        {QString::fromUtf8("modified"), LocallyModifiedUnstagedVersion},
        {QString::fromUtf8("added"), AddedVersion},
        {QString::fromUtf8("removed"), RemovedVersion},
        {QString::fromUtf8("conflict"), ConflictingVersion},
        {QString::fromUtf8("modifiedUnstaged"), LocallyModifiedUnstagedVersion},
        {QString::fromUtf8("ignored"), IgnoredVersion},
        {QString::fromUtf8("missing"), MissingVersion},
    };
    if (versions.contains(ver))
        return versions[ver];
    qWarning() << "====unknown version str: " << ver;
    return NormalVersion;
}

QList<QAction *> RabbitVcsPlugin::convertActions(QVector<const char *> names, RabbitVcsType vcs) const
{
    vcs = vcs == RabbitVcsType::none ? currentVcs : vcs;
    QList<QAction *> ret;
    if (actions.contains(vcs))
    {
        const auto &actionMap = actions[vcs];
        for (auto &name : names)
        {
            auto nameStr = QString::fromUtf8(name);
            if (actionMap.contains(nameStr))
                ret.append(actionMap[nameStr]);
        }
    }
    return ret;
}

void RabbitVcsPlugin::actionTriggered(RabbitVcsType vcs, const QString &name)
{
    currentVcs = vcs;   // for clone/checkout
    runRabbitVcsCmd(name);
}
